﻿using System.Windows.Forms;
using SharpDX.Windows;
using System;
using System.Drawing;
using OpenGameEngine.Input;
using System.Threading;

namespace OpenGameEngine.Base
{
    public abstract class GameBase : IDisposable
    {
        private Boolean _drawing;
        internal readonly GameTime clock = new GameTime();
        private Boolean _disposed;
        private Control _control;
        private float _frameAccumulator;
        private Int32 _frameCount;
        private GameConfiguration _gameConfig;

        public MouseStateInfo CurrentMouseStateInfo { get; private set; }
        public MouseStateInfo PreviousMouseStateInfo { get; private set; }

        public KeyboardStateInfo CurrentKeyboardStateInfo { get; private set; }
        public KeyboardStateInfo PreviousKeyboardStateInfo { get; private set; }

        #region IDisposable

        ~GameBase()
        {
            if (!_disposed)
            {
                Dispose(false);
                _disposed = true;
            }
        }

        public void Dispose()
        {
            if (!_disposed)
            {
                Dispose(true);
                _disposed = true;
            }

            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(Boolean disposeManagedResources)
        {
            if (disposeManagedResources)
            {
                if (_control != null)
                    _control.Dispose();
            }
        }
        #endregion

        protected IntPtr DisplayHandle
        {
            get { return _control.Handle; }
        }

        public GameConfiguration Config
        {
            get { return _gameConfig; }
        }

        public float FrameDelta { get; private set; }

        public float FramePerSecond { get; private set; }

        protected virtual Control CreateHostControl(GameConfiguration config)
        {
            Form f = new Form();
            f.ClientSize = new Size(config.Width, config.Height);
            f.MaximumSize = f.Size;
            f.MinimumSize = f.MaximumSize;
            f.Text = config.Title;

            return f;
        }

        public void Run()
        {
            Run(new GameConfiguration());
        }

        public void Run(GameConfiguration config)
        {
            _gameConfig = config ?? new GameConfiguration();
            _control = CreateHostControl(_gameConfig);
            Initialize(_gameConfig);

            Boolean isFormClosed = false;
            Boolean isFormResizing = false;

            CurrentKeyboardStateInfo = new KeyboardStateInfo();
            PreviousKeyboardStateInfo = new KeyboardStateInfo();
            CurrentMouseStateInfo = new MouseStateInfo();
            PreviousMouseStateInfo = new MouseStateInfo();

            _control.MouseMove += HandleMouseMove;
            _control.MouseDown += HandleMouseDown;
            _control.MouseUp += HandleMouseUp;
            _control.KeyUp += HandleKeyUp;
            _control.KeyDown += HandleKeyDown;
            _control.Resize += (o, args) =>
            {
                    HandleResize(o, args);
            };

            _control.HandleDestroyed += (o, args) => { isFormClosed = true; };

            LoadContent();

            clock.Start();
            BeginRun(clock);

            //try
            //{
                RenderLoop.Run(_control, () =>
                    {
                        if (isFormClosed)
                            return;

                        OnUpdate();

                        if (!isFormResizing)
                            Render();
                    });
            //}
            //catch (Exception e)
            //{
            //    MessageBox.Show(String.Format("{0}\n{1}\n{2}", "A unknown error occured while running the game", "Message: " + e.Message, "Stacktrace: " + e.StackTrace), "Unknown Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            //}


            UnloadContent();
            EndRun(clock);

            this.Dispose();
        }

        /// <summary>
        /// Initializes the game
        /// </summary>
        /// <param name="config"></param>
        protected abstract void Initialize(GameConfiguration config);

        /// <summary>
        /// Loads the game content
        /// </summary>
        protected virtual void LoadContent()
        {
        }

        /// <summary>
        /// Unloads the game content
        /// </summary>
        protected virtual void UnloadContent()
        {
        }

        /// <summary>
        /// Updates the game
        /// </summary>
        /// <param name="time">the current time</param>
        protected virtual void Update(GameTime time)
        {
        }

        /// <summary>
        /// Draws the game
        /// </summary>
        /// <param name="time">the game time</param>
        protected virtual void Draw(GameTime time)
        {

        }

        protected virtual void BeginRun(GameTime time)
        {
            OnUpdate();
        }

        protected virtual void EndRun(GameTime time)
        {
        }

        protected virtual void BeginDraw(GameTime time)
        {
            if (_drawing)
                throw new CoreException("Cannot begin drawing while it is already drawing", null, "GameBase.BeginDraw()");

            _drawing = true;
        }

        protected virtual void EndDraw(GameTime time)
        {
            if (!_drawing)
                throw new CoreException("Cannot end the drawing if it never started", null, "GameBase.EndDraw()");

            _drawing = false;
        }

        protected virtual void GameDeactivated(GameTime time)
        {

        }

        protected virtual void GameActivated(GameTime time)
        {

        }

        public void Exit()
        {
            _control.Dispose();
        }

        protected void OnUpdate()
        {
            FrameDelta = (float)clock.Update();

            while (FrameDelta < Config._upsms)
            {
                if ((Config._upsms - FrameDelta) > 0.0001)
                    Thread.Sleep(1);

                FrameDelta += (float)clock.Update();
            }

            Update(clock);

            PreviousMouseStateInfo = null;
            PreviousKeyboardStateInfo = null;

            PreviousKeyboardStateInfo = CurrentKeyboardStateInfo.Clone();
            PreviousMouseStateInfo = CurrentMouseStateInfo.Clone();
        }

        protected Size RenderingSize
        {
            get { return _control.ClientSize; }
            set { _control.ClientSize = value; }
        }

        private void Render()
        {
            _frameAccumulator += FrameDelta;
            ++_frameCount;
            if (_frameAccumulator >= 1.0f)
            {
                FramePerSecond = _frameCount / _frameAccumulator;
                _frameAccumulator = 0.0f;
                _frameCount = 0;
            }

            BeginDraw(clock);
            Draw(clock);
            EndDraw(clock);
        }

        protected virtual void MouseMove(MouseEventArgs e)
        {
            Int32 newX = e.X; //+((Int32)(e.X / 14));
            Int32 newY = e.Y; // +((Int32)(e.Y / 14));
            CurrentMouseStateInfo.SetLocation(new Vector2(newX, newY));
        }

        protected virtual void MouseUp(MouseEventArgs e)
        {
            CurrentMouseStateInfo[e.Button] = false;
        }

        protected virtual void MouseDown(MouseEventArgs e)
        {
            CurrentMouseStateInfo[e.Button] = true;
        }

        protected virtual void KeyDown(KeyEventArgs e)
        {
            CurrentKeyboardStateInfo[e.KeyData] = true;

            if (CurrentKeyboardStateInfo[Keys.Alt] && CurrentKeyboardStateInfo[Keys.F4])
                this.Exit();
        }

        protected virtual void KeyUp(KeyEventArgs e)
        {
            CurrentKeyboardStateInfo[e.KeyData] = false;
        }

        /// <summary>
        ///   Handles a mouse move event.
        /// </summary>
        /// <param name="sender">the sender</param>
        /// <param name="e"></param>
        private void HandleMouseMove(object sender, MouseEventArgs e)
        {
            MouseMove(e);
        }

        /// <summary>
        ///   Handles a mouse up event.
        /// </summary>
        /// <param name="sender">the sender</param>
        /// <param name="e"></param>
        private void HandleMouseUp(object sender, MouseEventArgs e)
        {
            MouseUp(e);
        }

        /// <summary>
        ///   Handles a mouse down event.
        /// </summary>
        /// <param name="sender">the sender</param>
        /// <param name="e"></param>
        private void HandleMouseDown(object sender, MouseEventArgs e)
        {
            MouseDown(e);
        }

        /// <summary>
        ///   Handles a key down event.
        /// </summary>
        /// <param name = "sender">The sender.</param>
        /// <param name = "e">The <see cref = "System.Windows.Forms.KeyEventArgs" /> instance containing the event data.</param>
        private void HandleKeyDown(object sender, KeyEventArgs e)
        {
            KeyDown(e);
        }

        /// <summary>
        ///   Handles a key up event.
        /// </summary>
        /// <param name = "sender">The sender.</param>
        /// <param name = "e">The <see cref = "System.Windows.Forms.KeyEventArgs" /> instance containing the event data.</param>
        private void HandleKeyUp(object sender, KeyEventArgs e)
        {
            KeyUp(e);
        }

        private void HandleResize(object sender, EventArgs e)
        {
            
        }

        private void HandleDeactivate(object sender, EventArgs e)
        {
            GameDeactivated(clock);
        }

        private void HandleActivate(object sender, EventArgs e)
        {
            GameActivated(clock);
        }
    }
}
